Skip to content

Phase 14e-2: domain-event listeners (file cleanup + cache invalidation)#32

Merged
bigin merged 1 commit into
imanager-2.0from
phase-14e2-listeners
May 3, 2026
Merged

Phase 14e-2: domain-event listeners (file cleanup + cache invalidation)#32
bigin merged 1 commit into
imanager-2.0from
phase-14e2-listeners

Conversation

@bigin
Copy link
Copy Markdown
Owner

@bigin bigin commented May 3, 2026

Summary

Scriptor-side companion to 14e-1 (iManager PSR-14 dispatcher + storage
emits). The container now wires a sync dispatcher on top of the
in-memory subscriber provider, hands it to SqliteStorage, and
subscribes two listeners.

Scriptor\Boot\Events\ItemFileCleanupListener (subscribes to
ItemDeleted):

  • Walks FileRepository::findByItem() while the row is still
    reachable (Plan §14e contract: dispatcher fires before the SQL
    DELETE so the FK cascade hasn't dropped the metadata yet) and asks
    FileStorage to drop each asset.
  • Also scrubs every <W>x<H>_<file> thumbnail under <dir>/thumbnail/
    via scandir, matching the upload convention. Tolerates missing
    dirs and unreadable siblings.

Scriptor\Boot\Events\PageCacheInvalidationListener (subscribes
to ItemCreated / ItemUpdated / ItemDeleted):

  • Filters by category id (Pages only — Users mutations don't
    invalidate the rendered-HTML cache).
  • Calls CacheInterface::clear(). BasicTheme keys cache entries by
    md5(host + REQUEST_URI), which we can't recreate at event-time,
    so this is a global flush until the cache learns tags / per-page
    prefixes.
    prefixes.

ImanagerBootstrap:

  • Registers SubscriberListenerProvider + alias to
    ListenerProviderInterface + EventDispatcherInterface
    (SyncEventDispatcher composing the provider).
  • SqliteStorage is now built with the dispatcher.
  • wireDomainEventListeners() subscribes both listeners through the
    provider; listener instances are constructed lazily on first fire,
    and the cache-invalidator caches its watched-category id after the
    first lookup.

composer.lock: pulls in psr/event-dispatcher 1.0.0 (transitively
through bigins/imanager at 14e-1).

Test plan

  • Create page → 302 (ItemCreated dispatched)
  • Upload image → 200; fileId, asset + 300×300 thumb on disk
  • Hit frontend → 1 cache file
  • Delete page → 302
  • After delete:
    • items row gone (FK cascade)
    • files row gone (FK cascade)
    • asset 14e2.png gone (ItemFileCleanupListener)
    • thumbnail/300x300_14e2.png gone (ItemFileCleanupListener)
    • cache files: 0 (PageCacheInvalidationListener)
  • Browser smoke against ServBay

Scriptor-side companion to 14e-1 (iManager PSR-14 dispatcher +
storage emits). Container now wires a sync dispatcher on top of the
in-memory subscriber provider, hands it to SqliteStorage, and
subscribes two listeners:

Scriptor\Boot\Events\ItemFileCleanupListener
  - Subscribes to ItemDeleted.
  - Walks FileRepository::findByItem() while the row is still
    reachable (Plan §14e contract: dispatcher fires before the SQL
    DELETE so the FK cascade hasn't dropped the metadata yet) and
    asks FileStorage to drop each asset.
  - Also scrubs every <W>x<H>_<file> thumbnail under <dir>/thumbnail/
    via FileStorage::absolutePath() + scandir, matching the upload
    convention (suffix `_<name>` plus a `\d+x\d+_` prefix). Tolerates
    missing dirs and unreadable siblings.

Scriptor\Boot\Events\PageCacheInvalidationListener
  - Subscribes to ItemCreated, ItemUpdated, ItemDeleted.
  - Filters by category id (Pages only — Users mutations don't
    invalidate the rendered-HTML cache).
  - Calls Cache::clear() on the FilesystemCache. BasicTheme keys
    cache entries by md5(host + REQUEST_URI), which we can't recreate
    at event-time, so this is a global flush of the section cache
    until the cache learns tags / per-page prefixes.

ImanagerBootstrap:
  - Registers SubscriberListenerProvider (typed bind) +
    ListenerProviderInterface (alias) + EventDispatcherInterface
    (SyncEventDispatcher composing the provider).
  - SqliteStorage is now built with the dispatcher.
  - wireDomainEventListeners() subscribes both listeners through the
    provider; listener instances are constructed lazily on first
    fire, and the cache-invalidator caches its watched-category id
    after the first lookup so it doesn't re-resolve on every event.

composer.lock: pulls in psr/event-dispatcher 1.0.0 (transitively
through bigins/imanager 14e-1).

Manual smoke (PHP built-in server, fresh PNG fixture):
  POST /editor/pages/edit/      → 302; new item id=11 created
  POST /editor/api/upload       → 200; fileId=5, asset + 300x300
                                  thumbnail on disk
  Frontend hit                  → 1 cache file
  GET /editor/pages/delete/?…   → 302
  Verifications after delete:
    items row gone               (0)
    files row gone               (0; FK cascade)
    asset 14e2.png gone          (ItemFileCleanupListener)
    thumbnail/300x300_14e2.png   gone (ItemFileCleanupListener)
    cache files                  0  (PageCacheInvalidationListener)
@bigin bigin merged commit d9baf0d into imanager-2.0 May 3, 2026
@bigin bigin deleted the phase-14e2-listeners branch May 15, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant